通过 SnapHelper 构建基于 RecyclerView 的轮播图
前言
Android 中的轮播图控件往往是基于 ViewPager 构建的,通过 setAdapter, addOnPageChangeListener,setPageTransformer 等 API 接口可以轻松实现轮播图的播放,滑动监听以及切换效果等功能,但是今天我们要另辟蹊径,通过基于 RecyclerView 这个高性能列表框架实现轮播图效果
技术背景
在 SupportLib 25.1.0 中 Android 官方为 RecyclerView 加入了一个新的类 SnapHelper,作用就是帮助 RecyclerView 滑动完成以后调整到合适的位置,并提供了两个官方子类实现 LinearSnapHelper 和 PagerSnapHelper,而其中 PagerSnapHelper 等效于 PageView 的滑动效果
SnapHelper
SnapHelper 顾名思义就是用来动态捕捉目标 view 的帮助类,比如说 PagerSnapHelper 会捕捉屏幕正中的 itemView 并通过动态计算实现 itemView 在滑动停止时居中
SnapHelper 是一个抽象类,实现了一些与 RecyclerView 相关监听方法,并且暴露了三个方法供子类实现
1 | /** |
大致的讲一下流程,SnapHelper 需要通过 attachToRecyclerView 与 RecyclerView 相关联
SnapHelper.attachToRecyclerView
1 | public void attachToRecyclerView(@Nullable RecyclerView recyclerView) |
主要通过 setupCallbacks 为 RecyclerView 添加 OnScrollListener 和设置 OnFlingListener,先从 OnScrollListener 开始
OnScrollListener 主要是为了监听 RecyclerView 滑动停止事件并及时调整位置效果
1 | private final RecyclerView.OnScrollListener mScrollListener = |
滑动以后 mScrolled 会设置滑动标志位,这样当滑动停止状态 RecyclerView.SCROLL_STATE_IDLE 触发以后才会调用 snapToTargetExistingView 去调整姿态。
SnapHelper.snapToTargetExistingView
1 | void snapToTargetExistingView() { |
该方法其实比较简单,通过 layoutManager 找到需要捕捉的目标 snapView,获取后再通过 layoutManager 和 snapView 计算出还需要滑动的距离最后通过 RecyclerView 平缓滑动。
还有一个关于快速滑动的特殊处理,fling 操作发生时为了防止出现快速滑动停止后有突然启动调整的情形,需要对 fling 的距离做特殊调整。 SnapHelper 实现了 RecyclerView.OnFlingListener 接口
1 | public boolean onFling(int velocityX, int velocityY) { |
通过 snapFromFling 方法确认是否需要拦截 fling 操作已替换成自己的逻辑实现,继续追踪
1 | private boolean snapFromFling(@NonNull LayoutManager layoutManager, int velocityX, |
在这个,通过 findTargetSnapPosition 方法获取到最终滑动的目标 view,可以是 fling 滑动一步到位,避免滑动停止后的调整操作,smoothScroller 由 createScroller 方法创建的,最终交给 createSnapScroller 实现,我们可以从 LinearSmoothScroller 类具体实现看出一些滑动细节
1 | protected LinearSmoothScroller createSnapScroller(LayoutManager layoutManager) { |
在 smoothScroller.setTargetPosition 设置了最终目标后,我们可以看到此时是可以得到滑动的 targetView 的,在 Scroller 的 onTargetFound 方法中需要通过 calculateDistanceToFinalSnap 去更新 action,使得最终的停止位置就是 snapView。
所以自定义的 SnapHelper 只需要实现几个抽象方法中的逻辑,我们回过头看 PageSnapHelper 的具体逻辑实现
PageSnapHelper
我们通过分析覆写的三个方法来切入
findSnapView
1 |
|
方法里对滑动方向做了区分,通过 findCenterView 来查找到相对居中的 view
1 | private View findCenterView(RecyclerView.LayoutManager layoutManager, |
通过 helper 可以比较容易的找到 snapView 那么计算出距离也是比较容易的,只需要将刚才计算的 RecyclerView 中心点和 SnapView 的中心点相减就行
1 | private int distanceToCenter(@NonNull RecyclerView.LayoutManager layoutManager, |
再看是如果计算出 fling 的目标位置 findTargetSnapPosition,由于 PagerSnapHelper 比较特殊,一次只能滑动一个 view
1 |
|
如果是正向滑动,那么返回下一个位置,因为 mStartMostChildView 还是原来的 view,如果是反向滑动,那么 mStartMostChildView 已经是前一个位置了,只要返回 centerPosition 即可。
在这个基础之上实现 ViewPager 的 OnPageChangeListener 就不难了,具体实现代码也不列举了,我已经在 github 上传了,https://github.com/sanousun/recyclerViewPager